/*!  \file  Queue.cpp
 *	  \brief  
 * 
 *   \details   details
 *   \author    Yordan Madzhunkov German Research School for Simulation Sciences GmbH
 *   \version   1.0
 *   \date      07/14/2013 05:26:13 PM
 *   \copyright GNU Public License.
 */
#include <string.h>
#include <pthread.h>
#include "macros.h"
#include "Lock.h"
#include "Queue.h"
#include "UnitTester.h"
#include <stdio.h>
#include <iomanip>      // std::setw



class TestQueue : public UnitTest {
public:
	TestQueue ();
	virtual ~TestQueue (){};
	virtual bool test() const;
	// given two sorted arrays counts how many common elements they have
	int count(int * RS first , const int nFirst, 
				 int * RS second, const int nSecond) const;

};

using namespace std;
TestQueue::TestQueue() {
	setName 				("Test Queue");
	setDescription  	("Test Queue for correct bahaviour & memory allocation");
	setTestAuthor		("Yordan Madzhunkov");
	setDeveloperAuthor("Yordan Madzhunkov");
	setCompileTime    (__DATE__, __TIME__); // each class that inherits this one should also have this line.
};
struct Params {
	Queue<int> * q;
	int * input;
	int * output;
	int id, nIn, nOut, nInFail, nOutFail;
	int nSamples;
};
#include "RandomGenerator.h"
void * start_routine1(void * arg) {
	Params * p = reinterpret_cast<Params *> (arg);
//	char buf[128];	sprintf(buf, "Hello I am thread %d\n", p->id); cout << buf;
	Queue<int> & q = *p->q;
	RandomGenerator * rng = RandomGenerator::defaultGenerator(p->id * 100 + 34);
	p->input  = new int[p->nSamples];
	p->output = new int[p->nSamples];
	p->nOut   = 0; 
	p->nIn    = 0;
	p->nInFail   = 0;
	p->nOutFail  = 0; // Queue is empty 

	for (int i = 0; i < p->nSamples; i++) {
		int id = i + p->id * p->nSamples;	
		if (rng->getUniform() < 0.50) {
			if (q.push(id, p->id))
				p->input[p->nIn++] = id;
			else p->nInFail++;
		} else {
			if (q.pop(id, p->id))
				p->output[p->nOut++] = id;
			else p->nOutFail++;
		}
	}
	int id = 0; 
	// pop until queue is empty or we run out of space
	while (p->nOut < p->nSamples && q.pop(id, p->id) ) {
		p->output[p->nOut++] = id;
	}
	if (rng) delete rng; rng = 0;
	sort( p->input, p->input  + p->nIn);
	sort(p->output, p->output + p->nOut);
	return NULL;
}
bool TestQueue::test() const {
	int nThreads = 4;
	Queue<int> q(16 * 16, nThreads);
	bool res = true;
	for (int i = 0; i < 10; i++) {
		q.push(i, 0);
	}
	for (int i = 0; i < 10; i++) {
		int r = 0;	q.pop(r, 0);
		res &= i == r;
	}
	assert(res);
	pthread_t * threadIDs = new pthread_t[nThreads];
	Params    * params    = new Params   [nThreads];
	q.reset();
	for (int i = 0; i < nThreads; i++) {
		params[i].q = &q;
		params[i].id = i;
		params[i].input  = NULL;
		params[i].output = NULL;
		params[i].nSamples =  256 * 256;
	}
	for (int i = 1; i < nThreads; i++) {
		void * arg = reinterpret_cast<void *> (&params[i]);	
#ifndef NDEBUG
		int info = 
#endif
		pthread_create(&threadIDs[i], NULL, &start_routine1, arg);
		assert(info == 0);
	}

	// use the current thread as well
	start_routine1(&params[0]);

	// join all theads	
	for (int i = 1; i < nThreads; i++) if (threadIDs[i]) {
		void * exitPtr = NULL;
		pthread_join(threadIDs[i], &exitPtr);	
		threadIDs[i] = 0;		
	}
	int c[nThreads][nThreads];
	int sumRows[nThreads]; memset(sumRows, 0, sizeof(sumRows));
	int sumCols[nThreads]; memset(sumCols, 0, sizeof(sumCols));
	for (int i = 0; i < nThreads; i++) {
		for (int j = 0; j < nThreads; j++) {
			c[i][j] = count(  params[i].input ,  params[i].nIn,
									params[j].output,  params[j].nOut);
			sumRows[i] += c[i][j];
			sumCols[j] += c[i][j];
		}
	}
	for (int i = 0; i < nThreads; i++) {
		res &=  sumRows[i] == params[i].nIn;
		res &=  sumCols[i] == params[i].nOut;
		if (params[i].output) delete [] params[i].output; 	params[i].output = 0;
		if (params[i].input ) delete [] params[i].input ; 	params[i].input  = 0;
	}
	if (threadIDs) delete [] threadIDs; threadIDs = 0;
	if (   params) delete []    params;    params = 0;
	return res;
}
int TestQueue::count(int * RS first , const int nFirst, 
				 int * RS second, const int nSecond) const {
	int f = 0, s = 0, res = 0;
	while (f < nFirst && s < nSecond) {
		if (first[f] == second[s]) {
			res++; f++; s++;
		} else if (first[f] < second[s]) f++;	else 	s++;
	}
	return res;
}
TestQueue testQueue;
